#pragma once
#include <cstdint>
#include <cmath>
#include <cassert>
#include <vector>
#include <tuple>
#include <algorithm>

#define M_PI 3.1415927
namespace AutoScale
{
	class Normal
	{
	public:
		Normal(int32_t peakReq, int32_t sigma) :
			peakReq_(peakReq), sigma_(sigma)
		{
			assert(sigma != 0);
		}

		int32_t operator()(int32_t step, int32_t duration) const
		{
			int32_t middle = duration >> 1;
			double d = step - middle;
			return (int32_t)(peakReq_ * exp(-d*d / (2 * sigma_ *sigma_)));
		}

	private:
		int32_t peakReq_;
		int32_t sigma_;
		
		double stepScale_;
	};

	class Uniform
	{
	public:
		Uniform(int32_t request) :
			request_(request)
		{
		}

		int32_t operator()(int32_t step, int32_t duration) const
		{
			return request_;
		}
	private:
		int32_t request_;
	};

	class Exponential
	{
	public:
		Exponential(int32_t peakReq, int32_t sigma_) :
			peakReq_(peakReq), sigma_(sigma_)
		{
		}

		int32_t operator()(int32_t step, int32_t duration) const
		{
			double d = step;
			return (int32_t)(peakReq_ * exp(-d / sigma_));
		}
	private:
		int32_t peakReq_;
		int32_t sigma_;
	};

	class KeyPath
	{
	public:
		KeyPath()
		{
		}

		int32_t operator()(int32_t step, int32_t duration) const
		{
			assert(!path_.empty());
			if (path_.size() == 1) return std::get<1>(path_.front());
			auto it = std::lower_bound(path_.begin(), path_.end(), step, [](std::tuple<int32_t, int32_t> const& lhs, int32_t rhs){
				return std::get<0>(lhs) < rhs;
			});
			if (it == path_.begin())
			{
				return std::get<1>(*it);
			}
			else if (it == path_.end())
			{
				return std::get<1>(path_.back());
			}
			else
			{
				auto prev = it - 1;
				return (std::get<1>(*prev) + std::get<1>(*it)) * (step - std::get<0>(*prev)) / (std::get<0>(*it) - std::get<0>(*prev));
			}
		}

		void AddKeyPoint(int32_t step, int32_t value)
		{
			assert(path_.empty() || step > std::get<0>(path_.back()));
			path_.push_back(std::make_tuple(step, value));
		}
	private:
		std::vector<std::tuple<int32_t, int32_t> > path_;
	};
}